package apibasej.basic.pool;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;

import apibasej.basic.config.ConfigManager;


public abstract class SimplePool<T extends ItemPool> extends ConfigManager{

	
	private int cursor = -1;
	private ItemPool[] pool;
	private ArrayList<ItemPool> poolTemp = new ArrayList<>(); //usado para itens que ultrapassem o temanho recomendado ... pior desempenho
	
	private int initialSize;
	private int recommendedSize;
	private int maxSize;
	
	// irá ficar lento nas primeira chamadas, pois irá preencher o pool, mas depois fica rápido
	
	public SimplePool(int initialSize, int recommendedSize, int maxSize) {
		super();
		this.initialSize = initialSize;
		this.recommendedSize = recommendedSize;
		this.maxSize = maxSize;
		pool = new ItemPool[initialSize];
	}

	
	public final synchronized T getItemPool() throws APIBasePoolException{
		return getItemPool(null);
	}
	
	
	@SuppressWarnings("unchecked")
	public final synchronized T getItemPool(Exception traceItem) throws APIBasePoolException{
		
		if(cursor==pool.length-1) {
			cursor = -1;// se estava posisionado no último, volta para antes do primeiro
		}
		
		++cursor; // avança o cursor
		
		for(int ct=0; ct<pool.length; ct++){
			int i = ct+cursor;
			if(i>=pool.length) {
				i-=pool.length;// para se ultrapaçar ler do início
			}
			//System.out.println("i="+i+" , ct="+ct+", cursor="+cursor+", lenPool="+pool.length);
			if(pool[i]==null || pool[i].isInvalidated()) {
				pool[i] = newItemPool();
			}
			if(!pool[i].isBusy()){
				pool[i].setBusy();
				return (T)pool[i];
			}
		}
		return incrementPool(traceItem);
	}
	
	
	@SuppressWarnings("unchecked")
	private synchronized T incrementPool(Exception traceItem) throws APIBasePoolException{
		if(pool.length+poolTemp.size()>=maxSize) {
			throw new APIBasePoolException("Pool size limit reached. Max.Size: "+maxSize+". Size pool: "+pool.length+", Size pool temp: "+poolTemp.size(),traceItem);
		}else if(pool.length>=recommendedSize){
			T itemTemp = newItemPool();
			itemTemp.setBusy();
			poolTemp.add(itemTemp);
			itemTemp.setPoolTemp(poolTemp);
			//usar exceção para registrar o trace, para poder identificar de onde chamou ... Mas não deve lançar a exceção!!!
			APIBasePoolException ex = new APIBasePoolException("New item pool temp created! "+itemTemp+". Size pool: "+pool.length+", Size pool temp: "+poolTemp.size(),traceItem);
			logDef(Level.WARNING,ex.getMessage(),ex);
			return itemTemp;
		}else{
			int lenBefore = pool.length;
			pool = Arrays.copyOf(pool, lenBefore+1);
			pool[lenBefore] = newItemPool();
			//ClientUtil.fine("New item pool created: "+pool[lenBefore]+". Size pool: "+pool.length);
			pool[lenBefore].setBusy();
			return (T)pool[lenBefore];
		}
	}
	
	public ArrayList<ItemPool> getPoolTemp() {
		return poolTemp;
	}
	
	// não colocar código lento nesta criação (pois pode enfilerar o getItemPool() que é sync), 
	// manter o construtor do ItemPool limpo, 
	// e consumir recursos somente quando realmente for utilizar o item do pool
	// deve ser protected para não correr o risco de chamar este método invés do getItemPool
	protected abstract T newItemPool() throws APIBasePoolException;

	
	public int getInitialSize() {
		return initialSize;
	}
	public int getMaxSize() {
		return maxSize;
	}
	public void setMaxSize(int maxSize) {
		this.maxSize = maxSize;
	}
	
	@Override
	protected void finalize() throws Throwable {
		invalidateAll();
	}

	public void invalidateAll(){
		for(ItemPool i : pool){
			if(i!=null)i.invalidate();
		}
	}

	public int getSize(){
		return pool.length;
	}
	public ItemPool[] getItems(){
		return pool;
	}
	
}
